/*
  Copyright (C) 2007 Jonas Nockert

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  See http://www.gnu.org/licenses/

  changes 0.1.3: Improved parsing of jaikufans.com links ending with /.
                  Added parsing of jaikufans.com/channel/...
          0.1.4: Removed files no longer needed.
          0.1.5: Added search box which links to jaiku.lemonad.org/search/
          0.1.6: Added channel search box which links to jaikufans/channel.
          0.1.7: Added read comments inline.
          0.1.8: Added commenters quick @-links under comment box.
          0.1.9: Improved commenters quick @-links through user images.
                 Fixed a rare formatting problem with inline comments.
         0.1.10: Some small tweaks to the above functionality.
         0.1.11: Adds a "top" link below comments to quickly return to the top (per request on Jaiku).
         0.1.12: Integrated search and channel search into one form.
         0.1.13: Submit without a search string no longer submits form.
                 Added FAQ link in search box.
                 Made the inline comment avatars clickable (thanks http://oyunfound.jaiku.com/presence/9194578 :)
                 Improved styling of some previous features.
                 Now works with Mozilla Firefox 3.0*.
                 Added a link underneath profile photos on main/comment pages for increasing image size.
                 Removed sandbox encapsulation.
                 Removed explicit use of XPCNativeWrapper as firefox 1.5+ uses it by default anyway.
         0.1.14: Fixed problem with iframes firing a second DOMContentLoaded event for non-adblock users.
                 Fixed problems with commas becoming part of autolink URLs.
                 No longer adding 'top'-link if there are no comments.
          0.2.0: Fixed a problem caused by Firefox issuing a DOMContentLoaded event on a tab reload.
                 Fixed a problem caused by Firefox issuing a DOMContentLoaded event on an empty tab.  
          0.2.1: Removed read inline option from active conversation sidebar.
          0.2.2: Added permalinks and jaikungfu favorite functionality at jaiku.lemonad.org/favorites/.
          0.2.3: Fixed a problem with read inline.
          0.2.4: Tooltip for favorite icon.
                 Confirm dialog for adding favorites.
                 Shortcut keys 1-9 for the first nine commenter @-quicklinks.
                 Improved Jaiku tab ordering for comment pages.
          0.2.5: The DRM Version :)SDionality.
                 Removed jaikungfu search form in right column.
          0.3.1: Fixed problems with preference dialog under linux (turned out it was due to firefox bug
                   296418).
                 Fixed problem of illegibility of top menu in combination with dark backgrounds or
                   images by adding background-color and border.
                 Added icons to preference dialog.
          0.3.2: Improved and fixed problems with favorites xpath. Now it avoids adding favorite icons
                   on most feed items.
          0.3.3: Added "links" to top jaikungfu menu.
                 Made the top menu visibility state sticky (i.e. it remembers if it's open or closed).
                 Fixed the preferences window for the umpteenth time. Window size is still calculated
                   incorrectly. Seems to be a known bug.
                 Set the max version to 3.0b4.
                 Made accesskeys configurable (since they conflict with OSX Spaces).
          0.3.4: Fixed problems with CSS that cut contacts and channels pages "in half".
          0.3.5: Added link to google translation
                 Fixed bug with @-quicklinks if there was over 10 commenters.
          0.3.6: Set extension compatibility so that future FF3 versions should deem it compatible.
          0.3.7: (Beta 1)
                 Fixed a problem with chrome images not loading. Added contentaccessible=yes to
                   chrome.manifest
                 Added a feature that @davidnilsson requested in Jaiku thread:
                   http://jaiku.com/channel/jaikungfu/presence/35831390
                 Added an experimental feature that @jocke requested in Jaiku thread:
                   http://jocke.jaiku.com/presence/35569977
          0.3.7: (Beta 2)
                 Fixed a bug which occurs on a fresh install since the contacts list preferences value
                   does not exist then.
          0.3.7: (Beta 3)
                 Problems with FF2 after adding contentaccessible=yes to chrome.manifest. This
                   is apparently not compatible. Workaround implemented in terms of following
                   the advice of comment#97 in https://bugzilla.mozilla.org/show_bug.cgi?id=292789.
          0.3.7: (Beta 4)
                 Comment forms are extended from around 4 rows to 8.
          0.3.8. Improved contact information on who's a contact (and who of them are not subscribing
                   to your updates. Also added information to top avatar on jaiku individual post pages.
                 Added auto-refresh for channels.
                 Rewrote AJAX calls.
                 Inline comment posting.
                 Improved auto-refresh rendering of content (channel information, etc.)
                 Fixed bug where not all commenter quicklinks were showing.
                 Improved error reporting when Jaiku API and/or feeds are down.
                 Fixed problem where Auto-refresh was triggering even though tab was closed.
          0.3.9. Fixed problem where contact information was not being reloaded when switching
                 between different jaiku accounts.
                 Fixed a couple of minor javascript issues (not noticeable to end users).
          0.3.10 Compatibility with Firefox 3.1b1.
                 Fixed a problem with resizing avatars of people who were not contacts. This bug
                   was likely introduced in 0.3.8.
                 Updated auto-refresh calls from synchronous ajax to asynchronous.
                 Automatically adds "@username: " to inline comment textfields.
          0.3.11 Compatibility with Firefox 3.1b2
          0.3.12 Added top link to jaikungfu menubar
          0.3.13 Fixed another problem with resizing avatars of people who were not contacts.
                   This bug was likely introduced in 0.3.8.
*/

function CJaikungfu() {
  this.inited = true;
}

CJaikungfu.jaikuLoggedInAs = null;
CJaikungfu.prefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('extensions.jaikungfu.');
CJaikungfu.contactsList = new Object;

CJaikungfu.lastAutoID = null;
CJaikungfu.refreshTimer = 0;
CJaikungfu.alreadySetupFlag = false;

CJaikungfu.ajaxRefresh = function(doc) {
  /* If document has no tab index, it's been closed or the tab is now showing
     a different URL */
  if(gBrowser.getBrowserIndexForDocument(doc) === -1) {
    return;
  }
  if(CJaikungfu.jaikuLoggedInAs !== null) {
    var href;
    if(doc.location.pathname.match(/^\/*channel\/.+/i)) {
      var href='http://jaiku.com/'+doc.location.pathname+'/feed/json';
    } else {
      var href='http://'+CJaikungfu.jaikuLoggedInAs+'.jaiku.com/contacts/feed/json';
    }

    var xpath, heading=null, sid, h2;

    if(CJaikungfu.refreshTimer !== 0) {
      clearTimeout(CJaikungfu.refreshTimer);
      CJaikungfu.refreshTimer = 0;
    }

    httpRequest = new XMLHttpRequest();
    if(httpRequest) {
      xpath = doc.evaluate(
        "//div[@id='stream']/h2/text()",
        doc,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null);
      if(xpath.singleNodeValue !== null) {
        heading = xpath.singleNodeValue;
        heading.nodeValue = 'Auto-refreshing...';
      } else {
        /* Remove heading above stream and create heading within stream */
        xpath = doc.evaluate(
          "//div[@id='content']//h2",
          doc,
          null,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null);
        if(xpath.singleNodeValue !== null) {
          xpath.singleNodeValue.parentNode.removeChild(xpath.singleNodeValue);
        }
        sid = doc.getElementById('stream');
        h2 = doc.createElement('h2');
        h2.appendChild(doc.createTextNode('Auto-refreshing...'));
        sid.insertBefore(h2, sid.firstChild);
      }

      httpRequest.open('GET', href, true);
      httpRequest.onreadystatechange = function(aEvt) {
        if(httpRequest.readyState === 4) {
          if(httpRequest.status !== 200) { // || !httpRequest.responseText
            if(heading !== null) {
              heading.nodeValue = 'Auto-update failed. Will try again in 60 seconds.';
            }
          } else {
            var b = httpRequest.responseText;
            if(b[0] !== '{') {
              if(heading !== null) {
                heading.nodeValue = 'Auto-update failed. Will try again in 60 seconds.';
              }
            } else {
              // create sandbox
              var j = Components.utils.Sandbox(doc.location.href);
              var k = Components.utils.evalInSandbox('('+b+')', j);
              var date = new Date();
              sid = doc.getElementById('stream');
              var parent = sid.parentNode;
              var d1, d2, ds, a1, t1, p1, p2, li, h3, u1, img, tmp, i2, iscmnt, isfeed, isphoto;
              ds = doc.createElement('div');
              ds.id = 'stream';
              h2 = doc.createElement('h2');
              h2.appendChild(doc.createTextNode('Auto-refreshed every 60 seconds'));
              ds.appendChild(h2);
              u1 = doc.createElement('ul');
              u1.className = 'stream';
              li = doc.createElement('li');
              li.className = 'date';
              h3 = doc.createElement('h3');
              h3.appendChild(doc.createTextNode('Last updated at ' + date.toLocaleTimeString()));
              li.appendChild(h3);
              u1.appendChild(li);

              for(var i=0; i<k.stream.length; i++) {
                iscmnt = 0;
                isfeed = 0;
                isphoto = 0;
                ischannelpost = 0;
                channel = '';
                li = doc.createElement('li');
                if(k.stream[i].comment_id) {
                  iscmnt = 1;
                  li.className='comment';
                } else if(k.stream[i].icon.length === 0 || k.stream[i].url.match(/^http:\/\/([A-Za-z0-9]+\.)*jaiku\.com/)) {
                  li.className='presence';
                } else if(k.stream[i].icon.length > 0 && k.stream[i].icon.match(/(fav-twitter|feed-atom)\.gif$/)) {
                  li.className='atom';
                  isfeed = 1;
                } else if(k.stream[i].icon.length > 0 && k.stream[i].icon.match(/fav-flickr\.gif$/)) {
                  li.className='photo';
                  isphoto = 1;
                  isfeed = 1;
                } else if(k.stream[i].icon.length > 0 && k.stream[i].icon.match(/fav-delicious\.gif$/)) {
                  li.className='bookmark';
                  isfeed = 1;
                } else {
                  li.className='presence';
                }

                match = /^http:\/\/jaiku\.com\/channel\/([a-z0-9]+)/i.exec(k.stream[i].url);
                if(match) {
                  ischannelpost = 1;
                  channel = match[1];
                }

                if(iscmnt) {
                  li.id='stream-comment-'+k.stream[i].comment_id;
                } else {
                  li.id='stream-'+k.stream[i].id;
                }

                d1 = doc.createElement('div');
                d1.className='top';
                d2 = doc.createElement('div');
                d2.className='bottom';
                a1 = doc.createElement('a');
                a1.className='url';
                a1.href = k.stream[i].user.url;
                img = doc.createElement('img');
                img.className='photo';
                img.alt=k.stream[i].user.nick;
                img.src=k.stream[i].user.avatar;

                if(!k.stream[i].user.nick.match(/^#/) &&
                              CJaikungfu.prefs.getBoolPref('contactslist') &&
                              CJaikungfu.jaikuLoggedInAs !== null &&
                              CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] !== undefined) {
                  if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][k.stream[i].user.nick] === undefined) {
                    img.className += ' jfu_nocontact';
                    img.title = "You're not following @" + k.stream[i].user.nick + " (but you might want to?)";
                  } else if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][k.stream[i].user.nick] === 0) {
                    img.className += ' jfu_halfcontact';
                    img.title = "@" + k.stream[i].user.nick + " is not following you.";
                  }
                }

                a1.appendChild(img);
                d2.appendChild(a1);

                if(iscmnt) {
                  tmp = k.stream[i].pretty_content;
                  tmp = tmp.replace(/^<p>/, '');
                  tmp = tmp.replace(/<\/p>/, '');
                  tmp = tmp.replace(/<p>/, '<br/><br/>');

                  p1 = doc.createElement('p');
                  p1.className='comment';
                  p1.innerHTML = tmp;
                  d2.appendChild(p1);
                } else {
                  if(k.stream[i].icon.length > 0) {
                    img = doc.createElement('img');
                    if(isphoto) {
                      /* JSON doesn't say if there's multiple images or not. Multiple images are
                         not linked in the Jaiku interface */
                      img.className = 'ext multiple';
                      img.alt = 'photo';
                      img.src = k.stream[i].content;
                    } else {
                      img.className = 'icon';
                      img.alt = 'icon';
                      img.src = k.stream[i].icon;
                    }
                    d2.appendChild(img);
                  }
                  h3 = doc.createElement('h3');

                  var parts = k.stream[i].title.split(/(http:\/\/[^ \t\r\n\(\)\[\],]+|[@#][a-zA-Z0-9]+)/);
                  for(i2=0; i2<parts.length; i2++) {
                    a1 = doc.createElement('a');
                    if(parts[i2].match(/^@[a-zA-Z0-9]+/)) {
                      a1.style.textDecoration='underline';
                      a1.href='http://' + parts[i2].substring(1) + '.jaiku.com/';
                    } else if(parts[i2].match(/^#[a-zA-Z0-9]+/)) {
                      a1.style.textDecoration='underline';
                      a1.href='http://jaiku.com/channel/' + parts[i2].substring(1) + '/';
                    } else if(parts[i2].match(/^http:\/\/[^ \t\r\n\(\)\[\],]+/)) {
                      a1.style.textDecoration='underline';
                      a1.href=parts[i2].replace(/[^a-zA-Z0-9_\/]+$/, '');
                    } else {
                      a1.href=k.stream[i].url;
                    }
                    a1.appendChild(doc.createTextNode(parts[i2]));
                    h3.appendChild(a1);
                  }
                  if(isfeed) {
                    h3.appendChild(doc.createTextNode(' '));
                    a1 = doc.createElement('a');
                    a1.className='more';
                    a1.href=k.stream[i].user.url+'/presence/'+k.stream[i].id;
                    a1.appendChild(doc.createTextNode('and more?'));
                    h3.appendChild(a1);
                  }
                  d2.appendChild(h3);
                }

                p2 = doc.createElement('p');
                p2.className='meta';
                if(iscmnt) {
                  p2.appendChild(doc.createTextNode('Comment from '));
                } else if(isfeed) {
                  p2.appendChild(doc.createTextNode('Fetched '));
                } else {
                  p2.appendChild(doc.createTextNode('By '));
                }
                if(!isfeed) {
                  a1 = doc.createElement('a');
                  a1.href = k.stream[i].user.url;
                  a1.className = 'url';
                  a1.appendChild(doc.createTextNode(k.stream[i].user.nick));
                  p2.appendChild(a1);
                }

                if(ischannelpost) {
                  p2.appendChild(doc.createTextNode(' to '));
                  a1 = doc.createElement('a');
                  a1.appendChild(doc.createTextNode('#' + channel));
                  a1.href = 'http://jaiku.com/channel/' + channel;  
                  p2.appendChild(a1);
                  p2.appendChild(doc.createTextNode(' '));
                }

                if(iscmnt) {
                  p2.appendChild(doc.createTextNode(' on '));
                  if(k.stream[i].url.match(/^http:\/\/[a-z0-9]+\.jaiku\.com/i)) {
                    nick = k.stream[i].url.replace(/^http:\/\/([a-z0-9]+)\.jaiku\.com.*$/i, '$1');
                    nickurl = k.stream[i].url.replace(/^(http:\/\/[a-z0-9]+\.jaiku\.com).*$/i, '$1');

                    a1 = doc.createElement('a');
                    a1.appendChild(doc.createTextNode(nick));
                    a1.href = nickurl;
                    p2.appendChild(a1);
                    if(nick.match(/s$/i)) {
                      nickending = "' ";
                    } else {
                      nickending = "'s ";
                    }
                    p2.appendChild(doc.createTextNode(nickending));
                  }

                  a1 = doc.createElement('a');
                  a1.href = k.stream[i].url;
                  a1.className = 'url';
                  a1.appendChild(doc.createTextNode(k.stream[i].entry_title));
                  p2.appendChild(a1);
                  p2.appendChild(doc.createTextNode(' ' + k.stream[i].created_at_relative + '. '));
                } else {
                  if(!isfeed && k.stream[i].content.length > 0) {
                    p2.appendChild(doc.createTextNode(' ' + k.stream[i].created_at_relative + ' in ' + k.stream[i].content + '. '));
                  } else {
                    p2.appendChild(doc.createTextNode(' ' + k.stream[i].created_at_relative + '. '));
                  }
                  a1 = doc.createElement('a');
                  if(isfeed) {
                    a1.href=k.stream[i].user.url+'/presence/'+k.stream[i].id+'#comments';
                  } else {
                    a1.href = k.stream[i].url+'#comments';
                  }

                  if(k.stream[i].comments !== '0') {
                    a1.className = 'comments';
                    a1.appendChild(doc.createTextNode(k.stream[i].comments + ' Comments'));
                    p2.appendChild(a1);
                    p2.appendChild(doc.createTextNode(' ('));
                    tmp = k.stream[i].url.replace(/#.*$/, '');
                    a1 = doc.createElement('a');
                    a1.addEventListener('click', CJaikungfu.readInlineHandler, false);
                    a1.href = tmp;
                    a1.appendChild(doc.createTextNode('Read inline'));
                    p2.appendChild(a1);
                    p2.appendChild(doc.createTextNode(') '));
                  } else {
                    a1.className = 'add-comment';
                    a1.appendChild(doc.createTextNode('Add Comment'));
                    p2.appendChild(a1);
                    p2.appendChild(doc.createTextNode(' '));
                  }
                }

                if(!isfeed && CJaikungfu.prefs.getBoolPref('favorites') && CJaikungfu.jaikuLoggedInAs !== null) {
                  a1 = doc.createElement('a');
                  a1.href = k.stream[i].url;
                  a1.addEventListener('click', CJaikungfu.addFavoriteHandler, false);
                  img = doc.createElement('img');
                  img.src = 'chrome://jaikungfu/skin/jaikungfu-fav.png';
                  img.alt = 'Add as favorite!';
                  img.title = 'Click to add this item as a favorite!';
                  img.className='jfu_fave';
                  a1.appendChild(img);
                  p2.appendChild(a1);
                }

                d2.appendChild(p2);
                d1.appendChild(d2);
                li.appendChild(d1);
                u1.appendChild(li);
              }
              ds.appendChild(u1);
              parent.replaceChild(ds, sid);
            }
          }
          setTimeout(CJaikungfu.ajaxRefresh, 60000, doc);
        }
      };
      httpRequest.send(null);
    }
  }
}

CJaikungfu.refreshHandler = function(e) {
  e.preventDefault();
  var cid, i;
  var doc=this.ownerDocument;

/* "Accept 3rd party cookies" need to be enabled in order for this to work.
   Should this be checked and alerted?

  if(CJaikungfu.prefs.getIntPref("network.cookie.cookieBehavior") != 0) {
    alert()
  } */

  if((cid = doc.getElementById('sub-nav')) !== null) {
    for(i=0; i<cid.childNodes.length; i++) {
      if(i === 0) {
        cid.childNodes[i].className = 'first';
      } else if(i === (cid.childNodes.length-1)) {
        cid.childNodes[i].className = 'current';
      } else {
        cid.childNodes[i].className = '';
      }
    }
    e.originalTarget.removeEventListener('click', CJaikungfu.refreshHandler, false);
    CJaikungfu.ajaxRefresh(doc);
  }
}

CJaikungfu.inlineCommentSubmitHandler = function(e) {
  e.preventDefault();
  var doc=e.originalTarget.ownerDocument;
  var href = e.originalTarget.action;
  
  var match = /\/presence\/([0-9]+)/.exec(href);
  if(!match) {
    alert('Error: Jaikungfu could not find presence ID in URL "' + href + '".');
    return false;
  }
  var pid = match[1];
  var sid = doc.getElementById('jaikungfu-comment-text-'+pid);
  var sid2 = doc.getElementById('jaikungfu-comment-nonce-'+pid);
  var sid3 = doc.getElementById('jaikungfu-comment-form-'+pid);
  if(!sid || !sid2 || !sid3) {
    alert('Error: Jaikungfu could not submit form because it could not find nonce or comment field.');
    return false;
  }

  var httpRequest = new XMLHttpRequest();
  if(!httpRequest) {
    alert('Error: Jaikungfu could not create an XMLHTTP instance for submitting inline comment form.');
    return false;
  }

  var params = '_nonce='+sid2.value+'&comment='+sid.value+'&submit=Submit Comment';
  httpRequest.open('POST', href, true);
  httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  httpRequest.setRequestHeader("Content-length", params.length);
  httpRequest.setRequestHeader("Connection", "close");
  httpRequest.onreadystatechange = function(aEvt) {
    if(httpRequest.readyState === 4) {
      if(httpRequest.status !== 200) {
        alert("Error: Jaikungfu could not post inline comment.");
      } else {
        sid3.style.display='none';
      }
    }
  };
  httpRequest.send(params);
}

CJaikungfu.ajaxCommentsFormCallback = function(doc, href, pid, httpRequest) {
  var b = httpRequest.responseText;
  var match;

  var sid = doc.getElementById('jaikungfu-comment-nonce-'+pid);
  var sid2 = doc.getElementById('jaikungfu-comment-form-'+pid);
  if(!sid || !sid2) {
    alert('Error: Jaikungfu could not find an element with ID "jaikungfu-comment-nonce-'+pid+'" in URL "' + href + '".');
    return;
  }

  // Fetch nonce
  match = /<input[^>]+name="_nonce"[^>]+value="([^"]+)"/i.exec(httpRequest.responseText);
  if(!match) {
    alert('Error: Jaikungfu could not find _nonce in URL "' + href + '".');
    return;
  }

  sid.value=match[1];
  sid2.style.display='block';
}

CJaikungfu.ajaxCommentsCallback = function(doc, pid, httpRequest) {
  var b = httpRequest.responseText;
  // create sandbox
  var j = Components.utils.Sandbox(doc.location.href);
  var k = Components.utils.evalInSandbox('('+b+')', j);

  var sid = doc.getElementById('jaikungfu-comment-form-'+pid);
  var parent = sid.parentNode;

  var d1, a1, t1;
  for(var i=0; i<k.comments.length; i++) {
    var li = doc.createElement('li');
    li.className='comment';
    d1 = doc.createElement('div');
    d1.className='top';
    var d2 = doc.createElement('div');
    d2.className='bottom';
    a1 = doc.createElement('a');
    a1.className='url';
    a1.href=k.comments[i].user.url;
    var img = doc.createElement('img');
    img.className='photo';
    img.alt=k.comments[i].user.nick;
    img.src=k.comments[i].user.avatar;

    if(!k.comments[i].user.nick.match(/^#/) &&
                  CJaikungfu.prefs.getBoolPref('contactslist') &&
                  CJaikungfu.jaikuLoggedInAs !== null &&
                  CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] !== undefined) {
      if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][k.comments[i].user.nick] === undefined) {
        img.className+=' jfu_nocontact';
        img.title = "You're not following this person (but you might want to?)";
      } else if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][k.comments[i].user.nick] === 0) {
        img.className+=' jfu_halfcontact';
        img.title = "This contact of yours is not following you.";
      }
    }
    a1.appendChild(img);
    d2.appendChild(a1);
    var p1 = doc.createElement('p');
    p1.className='comment';
    var tmp = k.comments[i].pretty_content;
    tmp = tmp.replace(/^<p>/, '');
    tmp = tmp.replace(/<\/p>/, '');
    tmp = tmp.replace(/<p>/, '<br/><br/>');
    p1.innerHTML = tmp;
    d2.appendChild(p1);
    var p2 = doc.createElement('p');
    p2.className='meta';
    t1 = doc.createTextNode(k.comments[i].created_at_relative + ' by ');
    p2.appendChild(t1);
    a1 = doc.createElement('a');
    a1.href=k.comments[i].user.url;
    t1 = doc.createTextNode(k.comments[i].user.nick);
    a1.appendChild(t1);
    p2.appendChild(a1);
    t1 = doc.createTextNode('. ');
    p2.appendChild(t1);
    a1 = doc.createElement('a');
    a1.className='comments';
    a1.href=k.comments[i].url;
    if(i == (k.comments.length-1)) {
      a1.id='jaikungfu-last-comment-'+pid;
    }
    t1 = doc.createTextNode('#' + (i+1));
    a1.appendChild(t1);
    p2.appendChild(a1);

    if(CJaikungfu.prefs.getBoolPref('favorites') && CJaikungfu.jaikuLoggedInAs !== null) {
      a1 = doc.createElement('a');
      a1.href = k.comments[i].url;
      a1.addEventListener('click', CJaikungfu.addFavoriteHandler, false);
      img = doc.createElement('img');
      img.src = 'chrome://jaikungfu/skin/jaikungfu-fav.png';
      img.alt = 'Add as favorite!';
      img.title = 'Click to add this item as a favorite!';
      img.className='jfu_fave';
      a1.appendChild(img);
      t1 = doc.createTextNode(' ');
      p2.appendChild(t1);
      p2.appendChild(a1);
    }

    t1 = doc.createTextNode('.');
    p2.appendChild(t1);
    d2.appendChild(p2);
    d1.appendChild(d2);
    li.appendChild(d1);
    parent.insertBefore(li, sid);
  }
}

CJaikungfu.readInlineHandler = function(e) {
  e.preventDefault();
  var doc=e.originalTarget.ownerDocument;
  var href=e.originalTarget.href;

  if(href !== '#') {
    var httpRequest = new XMLHttpRequest();
    var httpRequest_form = new XMLHttpRequest();
    if(!httpRequest || !httpRequest_form) {
      alert('Error: Jaikungfu could not create an XMLHTTP instance.');
      return false;
    }

    var match = /\/presence\/([0-9]+)/.exec(href);
    if(!match) {
      alert('Error: Jaikungfu could not find presence ID in URL "' + href + '".');
      return;
    }
    var pid = match[1];
    if(!doc.getElementById('jaikungfu-comment-form-'+pid)) {
      var sid = doc.getElementById('stream-'+pid);
      if(!sid) {
        alert('Error: Jaikungfu could not find an element with ID "stream-'+pid+'" in URL "' + href + '".');
        return;
      }
      var parent = sid.parentNode;
      var sibling = sid.nextSibling;
      var f1, d1, l1, t1, i1, h1, loading;

      if(doc.getElementById('jaikungfu-loading-'+pid) !== undefined) {
        l1 = doc.createElement('li');
        l1.className="date jfu_loading";
        loading = doc.createElement('h3');
        loading.id = 'jaikungfu-loading-'+pid;
        loading.appendChild(doc.createTextNode('Loading comments...'));
        l1.appendChild(loading);
        parent.insertBefore(l1, sibling);
      }

      f1 = doc.createElement('form');
      f1.style.display='none';
      f1.action = href;
      f1.method = 'post';
      f1.addEventListener('submit', CJaikungfu.inlineCommentSubmitHandler, false);
      f1.id = 'jaikungfu-comment-form-'+pid;
      d1 = doc.createElement('div');
      l1 = doc.createElement('label');
      l1.for = 'comment';
      l1.appendChild(doc.createTextNode('Your comment'));
      d1.appendChild(l1);
      t1 = doc.createElement('textarea');
      t1.id = 'jaikungfu-comment-text-'+pid;
      t1.name = 'comment';
      t1.rows = '2';
      t1.cols = '25';
      t1.style.height='2em';
      match = /^http:\/\/([a-z0-9]+)\.jaiku\.com/i.exec(href);
      if(match) {
        t1.value = '@' + match[1] +': ';
      }
      d1.appendChild(t1);
      f1.appendChild(d1);
      d1 = doc.createElement('div');
      d1.className = 'buttons';
      d1.style.display='inline';
      i1 = doc.createElement('input');
      i1.type = 'hidden';
      i1.name = '_nonce';
      i1.value = '';
      i1.id = 'jaikungfu-comment-nonce-'+pid;
      d1.appendChild(i1);
      i1 = doc.createElement('input');
      i1.type = 'submit';
      i1.value = 'Submit Content';
      d1.appendChild(i1);
      f1.appendChild(d1);
      parent.insertBefore(f1, sibling);
    }
    
    httpRequest.open('GET', href+'/json', true);
    httpRequest.onreadystatechange = function(aEvt) {
      if(httpRequest.readyState === 4) {
        loading = doc.getElementById('jaikungfu-loading-'+pid);
        
        if(httpRequest.status !== 200) {
          if(loading) {
            loading.firstChild.nodeValue = "Loading comments failed. Jaiku's API is probably down temporarily (" + httpRequest.status + ").";
          } else {
            alert("Loading comments failed. Jaiku's API is probably down temporarily (" + httpRequest.status + ").");
          }
        } else if(httpRequest.responseText) {
          if(!/^[ \n\r\t]*{[ \n\r\t]*"id"/.test(httpRequest.responseText)) {
            var busy = false;
            if(/Our servers are too busy now/i.test(httpRequest.responseText)) {
              busy = true;
            }
            if(loading) {
              if(busy) {
                loading.firstChild.nodeValue = "Loading comments failed. Jaiku's servers are too busy now.";
              } else {
                loading.firstChild.nodeValue = "Loading comments failed. Jaiku's API is probably down. Please wait a little while and try again.";
              }
            } else {
              if(busy) {
                alert("Loading comments failed. Jaiku's servers are too busy now.");
              } else {
                alert("Loading comments failed. Jaiku's API is probably not working at the moment. Please wait a while and try again.");
              }
            }
          } else {
            if(loading) {
              loading.parentNode.style.display='None';
            }
            CJaikungfu.ajaxCommentsCallback(doc, pid, httpRequest);
          }
        } else {
          if(loading) {
            loading.firstChild.nodeValue = "Loading comments failed. Jaiku's API is probably down temporarily.";
          } else {
            alert("Loading comments failed. Jaiku's API is probably down temporarily.");
          }
        }
      }
      };
    httpRequest.send(null);

    httpRequest_form.open('GET', href, true);
    httpRequest_form.onreadystatechange = function(aEvt) {
      if(httpRequest_form.readyState === 4) {
        if(httpRequest_form.status !== 200) {
          alert("Error: Jaikungfu did not get a proper response back from the\nJaiku servers, perhaps temporarily down?");
        } else if(httpRequest_form.responseText) {
          CJaikungfu.ajaxCommentsFormCallback(doc, href, pid, httpRequest_form);
        } else {
          alert('Error: Jaikungfu got HTTP status code 200 from Jaiku but no content.');
        }
      }
    };
    httpRequest_form.send(null);
  }
}

CJaikungfu.parseContacts = function(doc) {
  var i, vcard, mutual, json, cnt;
  var nicks = new Object;
  var contacts = doc.evaluate(
      "//div[@id='contacts']/ul[@class='float']/li[contains(@class,'vcard')]/h3/a[@class='url']/text()",
      doc,
      null,
      XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
      null);
  if(contacts.snapshotLength > 0) {
    nicks[CJaikungfu.jaikuLoggedInAs] = 1;
    for(i=0; i<contacts.snapshotLength; i++) {
      vcard = contacts.snapshotItem(i).parentNode.parentNode.parentNode.className;
      if(vcard && vcard.match(/mutual/)) {
        mutual = 1;
      } else {
        mutual = 0;
      }
      nicks[contacts.snapshotItem(i).nodeValue.replace(/\s+/g, '')] = mutual;
    }
    json = '{';
    for (var nick in nicks) {
      json += '"' + nick + '":' + nicks[nick] + ',';
    }
    json += '}';
    CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] = nicks;
    CJaikungfu.prefs.setCharPref(CJaikungfu.jaikuLoggedInAs + '_contactslist', json);
  } else {
    alert("Jaikungfu Error: Could not parse contacts.");
  }
}

CJaikungfu.menuHandler = function(e) {
  e.preventDefault();
  var fid;
  var doc=e.target.ownerDocument;
  if((fid = doc.getElementById('jaikungfu_menu')) !== null) {
    if(CJaikungfu.prefs.getBoolPref('menuopen')) {
      fid.style.visibility = 'hidden';
      CJaikungfu.prefs.setBoolPref('menuopen', false);
    } else {
      fid.style.visibility = 'visible';
      CJaikungfu.prefs.setBoolPref('menuopen', true);
    }
  }
}

CJaikungfu.searchMyselfHandler = function(e) {
  e.preventDefault();
  var fid, iid;
  var doc=e.target.ownerDocument;
  
  var fn1 = doc.createElement('form');
  fn1.method='post';
  fn1.action='http://jaiku.lemonad.org/search/';
  fn1.name='search';
  fn1.style.display='none';
  var in1 = doc.createElement('input');
  in1.type='text';
  in1.name='search';
  in1.value=CJaikungfu.jaikuLoggedInAs;
  fn1.appendChild(in1);
  in1 = doc.createElement('input');
  in1.type='submit';
  fn1.appendChild(in1);
  doc.body.appendChild(fn1);
  fn1.submit();
}

CJaikungfu.addFavoriteHandler = function(e) {
  e.preventDefault();
  var fid, iid;
  var doc=e.target.ownerDocument;
  if((fid = doc.getElementById('jaikungfu-form-favorite')) !== null) {
    if((iid = doc.getElementById('jaikungfu-form-favorite-url')) !== null) {
      if(confirm('Add to favorites?')) {
        iid.value=this.href;
        fid.submit();
      }
    }
  }
}

CJaikungfu.resizeAvatarHandler = function(e) {
  e.preventDefault();
  var doc=e.target.ownerDocument;
  var avatar = doc.evaluate(
      "//div[@id='profile']/img[contains(@class, 'photo')]|//div[@id='current-stream']/div/a/img[contains(@class, 'photo')]",
      doc,
      null,
      XPathResult.FIRST_ORDERED_NODE_TYPE,
      null);
  if(avatar.singleNodeValue !== null) {
    avatar.singleNodeValue.className=' jfu_avatar_xl';
    avatar.singleNodeValue.src = avatar.singleNodeValue.src.replace(/_f\.jpg/, '_m.jpg');
    this.style.display='none';
  }
}

CJaikungfu.commentLinksHandler = function(e) {
  var cid;
  var doc=e.target.ownerDocument;
  e.preventDefault();
  if((cid = doc.getElementById('comment')) !== null) {
    if(cid.nodeName === 'TEXTAREA' || cid.nodeName === 'textarea') {
      var ss = cid.selectionStart;
      var se = cid.selectionEnd;
      if(ss === 0) {
        cid.value = this.firstChild.nodeValue + ': ' + cid.value.substring(se, cid.value.length);
      } else {
        cid.value = cid.value.substring(0, ss) + this.firstChild.nodeValue + cid.value.substring(se, cid.value.length);
      }
      cid.focus();
    }
  }
}

CJaikungfu.addContactInfo = function(doc, xpathstr, type) {
  /*
    Type: stream = 0, sidebar = 1, and top = 2
  */
  if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] === undefined) {
    return;
  }
  var xpath = doc.evaluate(xpathstr, doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  var i, nick, nickimg, parent, n, d;
  if(xpath.snapshotLength > 0) {
    for(i=0; i<xpath.snapshotLength; i++) {
      nickimg = xpath.snapshotItem(i);
      nick = nickimg.alt;
      if(!nick.match(/^#/)) {
        if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][nick] === undefined) {
          if(type===1) {
            nickimg.className+=' jfu_nocontact_sb';
          } else if(type===2) {
            nickimg.className+=' jfu_nocontact_top';
            nickimg.title="You're not following this person (but you might want to?)";
          } else {
            nickimg.className+=' jfu_nocontact';
            nickimg.title="You're not following this person (but you might want to?)";
          }
        } else if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs][nick] === 0) {
          if(type===1) {
            nickimg.className+=' jfu_halfcontact_sb';
          } else if(type===2) {
            nickimg.className+=' jfu_halfcontact_top';
            nickimg.title = "This contact of yours is not following you.";
          } else {
            nickimg.className+=' jfu_halfcontact';
            nickimg.title = "This contact of yours is not following you.";
          }
        }
      }
    }
  }
}

CJaikungfu.contentLoaded = function(e) {
  /*
  var startDate = new Date();
  */
  var doc = e.originalTarget;
  if (doc.defaultView.frameElement) {
    // Frame within a tab was loaded
    return;
  }

  // empty tabs have protocol but not hostname
  if(doc.location.protocol !== 'http:') {
    return;
  }

  var href=doc.location.href;
  var hostname=doc.location.hostname;
  var pathname=doc.location.pathname;
  var hash=doc.location.hash;

  if((hostname !== 'jaiku.com' && !/^[A-Za-z0-9]+\.jaiku\.com$/.test(hostname))) {
    return;
  }

  /* Not sure if this could ever occur */
  if(doc.getElementById('jaikungfu_has_done_its_thing') !== null) {
    return;
  }

  /* Check if any of the preferences are on and if so, add menu */
  var cid, an1, tn1, dn1, ul1, li1;
  if(CJaikungfu.prefs.getBoolPref('miscellaneous') ||
     CJaikungfu.prefs.getBoolPref('addcommenters') ||
     CJaikungfu.prefs.getBoolPref('readinline') ||
     CJaikungfu.prefs.getBoolPref('autolink') ||
     CJaikungfu.prefs.getBoolPref('autorefresh') ||
     CJaikungfu.prefs.getBoolPref('morecontext') ||
     CJaikungfu.prefs.getBoolPref('favorites')) {
    if((cid = doc.getElementById('login')) !== null) {
      if(cid.nodeName === 'P' || cid.nodeName === 'p') {
        if(cid.childNodes.length > 1 && cid.childNodes[0].nodeType === 3 &&
                                       (cid.childNodes[1].nodeName === 'A' ||
                                        cid.childNodes[1].nodeName === 'a') &&
                                       !cid.childNodes[1].href.match(/^http:\/\/jaiku\.com\/login\/*\?/) &&
                                       !cid.childNodes[1].href.match(/^http:\/\/jaiku\.com\/invite\//)) {
          CJaikungfu.jaikuLoggedInAs = (cid.childNodes[1].childNodes[0].nodeValue);
        } else {
          CJaikungfu.jaikuLoggedInAs = null;
        }
        
        /* Add jaikungfu style sheet */
        var h = doc.getElementsByTagName('head');
        h = h[0];
        l = doc.createElement('link')
        l.href = 'chrome://jaikungfu/content/jaikungfu.css';
        l.setAttribute('media', 'screen')
        l.setAttribute('rel', 'stylesheet');
        l.setAttribute('type', 'text/css');
        h.appendChild(l);
        
        /* Create Jaikungfu menu */
        cid.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='#';
        an1.id = 'jaikungfu_has_done_its_thing';
        an1.addEventListener('click', CJaikungfu.menuHandler, false);
        var new_img = doc.createElement('img');
        new_img.src = 'chrome://jaikungfu/skin/jaikungfu16.png';
        new_img.style.verticalAlign = 'middle';
        new_img.style.display = 'inline';
        an1.appendChild(new_img);
        cid.appendChild(an1);
        
        var new_div = doc.createElement('p');
        new_div.id = 'jaikungfu_menu';
        if(!CJaikungfu.prefs.getBoolPref('menuopen')) {
          new_div.style.visibility = 'hidden';
        }
        an1 = doc.createElement('a');
        an1.href='http://jaiku.com/channel/jaikungfu';
        an1.appendChild(doc.createTextNode('#jaikungfu'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        if(CJaikungfu.jaikuLoggedInAs !== null) {
          an1.href='http://jaiku.lemonad.org/favorites/?user=' + CJaikungfu.jaikuLoggedInAs;
        } else {
          an1.href='http://jaiku.lemonad.org/favorites/';
        }
        an1.appendChild(doc.createTextNode('favorites'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='http://jaiku.lemonad.org/search';
        an1.appendChild(doc.createTextNode('search'));
        new_div.appendChild(an1);

        if(CJaikungfu.jaikuLoggedInAs !== null) {
          new_div.appendChild(doc.createTextNode(' ('));
          an1 = doc.createElement('a');
          an1.href='http://jaiku.lemonad.org/search';
          an1.appendChild(doc.createTextNode('@' + CJaikungfu.jaikuLoggedInAs));
          an1.addEventListener('click', CJaikungfu.searchMyselfHandler, false);
          new_div.appendChild(an1);
          new_div.appendChild(doc.createTextNode(')'));
          new_div.appendChild(doc.createElement('br'));
        } else {
          new_div.appendChild(doc.createTextNode(' | '));
        }

        an1 = doc.createElement('a');
        an1.href='http://jaiku.lemonad.org/top/';
        an1.appendChild(doc.createTextNode('top'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='http://jaiku.lemonad.org/trends/';
        an1.appendChild(doc.createTextNode('trends'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='http://jaiku.lemonad.org/channels/';
        an1.appendChild(doc.createTextNode('channels'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='http://jaiku.lemonad.org/links/';
        an1.appendChild(doc.createTextNode('links'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='http://google.com/translate?u=' + href;
        an1.appendChild(doc.createTextNode('translate'));
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='#';
        an1.appendChild(doc.createTextNode('update contacts list'));
        an1.href='http://jaiku.com/contacts/contacts'
        new_div.appendChild(an1);

        new_div.appendChild(doc.createTextNode(' | '));
        an1 = doc.createElement('a');
        an1.href='#';
        an1.appendChild(doc.createTextNode('hide'));
        an1.addEventListener('click', CJaikungfu.menuHandler, false);
        new_div.appendChild(an1);
        
        cid.parentNode.insertBefore(new_div, cid.nextSibling);
      }
    }
  }

  if(CJaikungfu.prefs.getBoolPref('autorefresh') && (hostname === 'jaiku.com' || hostname === 'www.jaiku.com')) {
    if((cid = doc.getElementById('sub-nav')) !== null) {
      /* overview */
      li1 = doc.createElement('li');
      an1 = doc.createElement('a');
      an1.href = '#';
      an1.appendChild(doc.createTextNode('Auto-refresh'));
      an1.addEventListener('click', CJaikungfu.refreshHandler, false);
      li1.appendChild(an1);
      cid.appendChild(li1);
    } else if((cid = doc.getElementById('form-message')) != null) {
      /* Currently only overview and channels have form-messages so if it's not
         your overview, it has to be a channel */
      ul1 = doc.createElement('ul');
      ul1.id = 'sub-nav';

      li1 = doc.createElement('li');
      li1.className = 'first current';
      an1 = doc.createElement('a');
      an1.href = href;
      an1.appendChild(doc.createTextNode('Channel Overview'));
      li1.appendChild(an1);
      ul1.appendChild(li1);

      li1 = doc.createElement('li');
      an1 = doc.createElement('a');
      an1.href = '#';
      an1.appendChild(doc.createTextNode('Auto-refresh'));
      an1.addEventListener('click', CJaikungfu.refreshHandler, false);
      li1.appendChild(an1);
      ul1.appendChild(li1);
      cid.appendChild(ul1);
    }
  }

  if(CJaikungfu.prefs.getBoolPref('miscellaneous')) {
    if(/\/presence\/[0-9]+/.test(pathname)) {
      // Add top link before form on comment pages
      if((cid = doc.getElementById('comments')) !== null) {
        if(cid.nodeName === 'DIV' || cid.nodeName === 'div') {
          // more than 3 children means there's more than just an h2.
          if(cid.childNodes.length > 3) {
            dn1 = doc.createElement('div');
            dn1.style.textAlign='right';
            an1 = doc.createElement('a');
            an1.href='#';
            an1.appendChild(doc.createTextNode('top'));
            dn1.appendChild(an1);
            cid.appendChild(dn1);
          }
        }
      }
    }

    // Add resize link underneath user main page photo
    xpath = doc.evaluate(
        "//div[@id='profile']",
        doc,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null);
    if(xpath.singleNodeValue !== null) {
      dn1 = doc.createElement('div');
      dn1.className='jfu_resize_main';
      an1 = doc.createElement('a');
      an1.href='#';
      an1.addEventListener('click', CJaikungfu.resizeAvatarHandler, false);
      an1.appendChild(doc.createTextNode('[+]'));
      dn1.appendChild(an1);
      var par = xpath.singleNodeValue;
      var chi = xpath.singleNodeValue.firstChild;
      par.insertBefore(dn1, chi);
    }

    // Add resize link underneath user comment page photo
    xpath = doc.evaluate(
        "//div[@id='current-stream']/a/img[@class='photo']",
        doc,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null);
    if(xpath.singleNodeValue !== null) {
      var copy = xpath.singleNodeValue.parentNode.cloneNode(true);
      copy.style.textDecoration='none';
      copy.firstChild.style.cssFloat='none';
      dn1 = doc.createElement('div');
      dn1.className='jfu_resize_user';
      dn1.appendChild(copy);
      an1 = doc.createElement('a');
      an1.href='#';
      an1.style.color='#009bc7';
      an1.addEventListener('click', CJaikungfu.resizeAvatarHandler, false);
      an1.appendChild(doc.createTextNode('[+]'));
      dn1.appendChild(an1);
      var par = xpath.singleNodeValue.parentNode.parentNode;
      var chi = xpath.singleNodeValue.parentNode;
      par.replaceChild(dn1, chi);
    }
  }

  // Add favorites links
  if(CJaikungfu.prefs.getBoolPref('favorites') && CJaikungfu.jaikuLoggedInAs !== null) {
    var new_a, new_img, new_t, element, permalink;
    xpath = doc.evaluate(
        "//div[@id='stream']/ul[@class='stream']/li[@class='comment' or @class='presence']//p[@class='meta']|//div[@id='comments']/ul/li//p[@class='meta']|//div[@id='current-stream']//p[@class='meta']",
        doc,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);
    if(xpath.snapshotLength > 0) {
      new_a = doc.createElement('a');
      new_img = doc.createElement('img');
      new_img.src = 'chrome://jaikungfu/skin/jaikungfu-fav.png';
      new_img.className='jfu_fave';
      new_img.alt = 'Add as favorite!';
      new_img.title = 'Click to add this item as a favorite!';
      new_a.appendChild(new_img);
      
      for(i=0; i<xpath.snapshotLength; i++) {
        element = xpath.snapshotItem(i);
        permalink = 0;
        for(j=0; j<element.childNodes.length; j++) {
          if(element.childNodes[j].className === 'add-comment' || element.childNodes[j].className === 'comments' ||
             /#c\-[0-9]+$/.test(element.childNodes[j].href)) {
            permalink = element.childNodes[j].href;
            break;
          }
        }
        if(permalink === 0) {
          permalink = href.replace(/[\?#].*$/, '');

          var e2 = element.parentNode;
          for(j=0; j<5; j++) {
            if((e2.nodeName === 'li' || e2.nodeName === 'LI') && /^c\-[0-9]+$/.test(e2.id)) {
              permalink = permalink + '#' + e2.id;
              break;
            } else {
              e2 = e2.parentNode;
            }
          }
        } else {
          permalink = permalink.replace(/#comments$/, '');
        }

        an1 = new_a.cloneNode(true);
        an1.addEventListener('click', CJaikungfu.addFavoriteHandler, false);
        an1.href = permalink;
        element.appendChild(doc.createTextNode(' '));
        element.appendChild(an1);
      }

      // Add hidden form to jaikungfu icon at the top of the page
      // when submitted by a favorite icon, the form will contain your
      // jaiku username and the presence url you want to favorite.
      //
      // The privacy implications are slim to none, e.g. if a person
      // has set their Jaiku updates to be private and _accidentally_
      // favorites someones public jaiku, that theoretically says
      // something about that person. It is identical to a scenario
      // where a person would accidentally post to a public thread.
      //
      // It's not possible to favorite a private conversation and make
      // it public because Jaiku does not expose those threads through
      // their Json API.
      if((cid = doc.getElementById('jaikungfu_has_done_its_thing')) !== null) {
        var fn1 = doc.createElement('form');
        fn1.style.display='none';
        fn1.id='jaikungfu-form-favorite';
        fn1.method='post';
        fn1.action='http://jaiku.lemonad.org/favorites/add.php';
        var in1 = doc.createElement('input');
        var in2 = in1.cloneNode(false);
        var in3 = in1.cloneNode(false);
        in1.type='hidden';
        in1.name='user';
        in1.value=CJaikungfu.jaikuLoggedInAs;
        fn1.appendChild(in1);
        in2.id='jaikungfu-form-favorite-url';
        in2.type='hidden';
        in2.name='presence_url';
        fn1.appendChild(in2);
        in3.type='submit';
        in3.value='Add Favorite';
        fn1.appendChild(in3);
        cid.parentNode.appendChild(fn1);
      }
    }
  }

  if(CJaikungfu.prefs.getBoolPref('addcommenters') && /\/presence\/[0-9]+/.test(pathname)) {
    var item, parent, new_node, new_img, first=1, new_text_node, new_a, in1;
    var usernames = [];
    var userimgs = [];

    if((cid = doc.getElementById('comment')) !== null) {
      if(cid.nodeName === 'TEXTAREA' || cid.nodeName === 'textarea') {
        // fix jaiku tab ordering, first comment textarea and then submit button
        cid.tabIndex=1;
        cid.style.height="12em";
        xpath = doc.evaluate(
                "//div[@id='leave-comment']//input[@type='submit']",
                doc,
                null,
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null);
        if(xpath.singleNodeValue !== null) {
          xpath.singleNodeValue.tabIndex = 2;
        }

        xpath = doc.evaluate(
                "//a[@class='url']/img[@class='photo' and @alt]",
                doc,
                null,
                XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
                null);
        while((item = xpath.iterateNext()) !== null) {
          if(item.alt.length > 0 && userimgs[item.alt] === undefined) {
              usernames.push(item.alt);
              userimgs[item.alt] = item.src;
          }
        }

        usernames.sort(function(x,y) {
          var a = String(x).toLowerCase(); 
          var b = String(y).toLowerCase();
          if (a > b) { return(1); }
          if (a < b) { return(-1); }
          return(0);
        });

        new_a = doc.createElement('a');
        new_a.href='#';

        parent = cid.parentNode;
        new_node = doc.createElement('p');
        new_node.className = 'help';
        new_node.appendChild(doc.createTextNode('Click to insert screen name (or use keyboard shortcuts 1-9, see '));
        an1 = new_a.cloneNode(false);
        an1.href = 'http://jaiku.lemonad.org/faq/extension/';
        an1.appendChild(doc.createTextNode('FAQ'));
        new_node.appendChild(an1);
        new_node.appendChild(doc.createTextNode('): '));
        new_img = doc.createElement('img');
        new_img.src = userimgs[usernames[i]];
        new_img.className='jfu_commenter';
        new_text_node = doc.createTextNode(', ');

        var akey;
        for(i=0; i<usernames.length; i++) {
          if(first) {
            first = 0;
          } else {
            new_node.appendChild(new_text_node.cloneNode(false));
          }
          an1 = new_a.cloneNode(false);
          an1.addEventListener('click', CJaikungfu.commentLinksHandler, false);
          
          if(i<10) {
            akey = CJaikungfu.prefs.getCharPref('accesskey'+((i+1) % 10));
            if(akey.length == 1) {
              an1.accessKey = akey;
              new_node.appendChild(doc.createTextNode('['+akey+'] '));
            }
          }
          an1.appendChild(doc.createTextNode('@'+usernames[i]));
          new_node.appendChild(an1);

          in1 = new_img.cloneNode(false);
          in1.src = userimgs[usernames[i]];
          new_node.appendChild(in1);
        }
        parent.insertBefore(new_node, cid.nextSibling);
      }
    }
  }

  if(CJaikungfu.prefs.getBoolPref('readinline')) {
    var item, parent, sibling, new_node, ihref;

    new_node = doc.createElement('a');
    new_node.appendChild(doc.createTextNode('Read inline'));
    xpath = doc.evaluate(
      "//div[@id='content']//li[@class='presence' or @class='bookmark' or @class='event' or @class='blog' or @class='atom' or @class='photo' or @class='music']//p[@class='meta']/a[@class='comments']",
      doc,
      null,
      XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
      null);
    for(i=0; i<xpath.snapshotLength; i++) {
      item = xpath.snapshotItem(i);
      parent = item.parentNode;
      sibling = item.nextSibling;
      parent.insertBefore(doc.createTextNode(' ('), sibling);

      ihref = item.href.replace(/#.*$/, '');
      an1 = new_node.cloneNode(true);
      an1.addEventListener('click', CJaikungfu.readInlineHandler, false);
      an1.href = ihref;
      parent.insertBefore(an1, sibling);
      parent.insertBefore(doc.createTextNode(')'), sibling);
    }
  }

  // is jaikungfu auto-linking enabled?
  if(CJaikungfu.prefs.getBoolPref('autolink')) {
    var text;
    var regexp = /(http:\/\/[^ \t\r\n\(\)\[\],]+)/;
  
    xpath = doc.evaluate(
      "//h2//text()|//p/text()|//h3/a[contains(@href,'jaiku.com') and not(@class)]|//div[@class='vcard']/p[@class='message']/a[contains(@href, '.jaiku.com/presence/last')]|//p[@class='message']//a[contains(@href, '.jaiku.com/presence/last')]",
      doc,
      null,
      XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
      null);
  
    for(i=0; i<xpath.snapshotLength; i++) {
      text = xpath.snapshotItem(i);
      if(text.nodeType === text.ELEMENT_NODE) {
        if(text.nodeName === 'A' && text.childNodes.length === 1 && 
                                    text.childNodes[0].nodeType === text.TEXT_NODE &&
                                    text.childNodes[0].nodeValue.match(regexp)) {
          var parent = text.parentNode;
          var parts = text.childNodes[0].nodeValue.split(regexp);
          for(j=0; j<parts.length; j++) {
            var text_node = doc.createTextNode(parts[j]);
            var new_node = text.cloneNode(false);
            if(parts[j].match(/^http:\/\/[^ \t\r\n]+/)) {
              parts[j] = parts[j].replace(/[^a-zA-Z0-9_\/]+$/, '');
              new_node.href = parts[j];
              new_node.style.textDecoration='underline';
              new_node.appendChild(text_node);
              parent.insertBefore(new_node, text);
            } else {
              new_node.appendChild(text_node);
              parent.insertBefore(new_node, text);
            }
          }
          parent.removeChild(text);
        }
      } else if(text.nodeType === text.TEXT_NODE && text.nodeValue.match(regexp)) {
        var parent = text.parentNode;
        var parts = text.nodeValue.split(regexp);
        for(j=0; j<parts.length; j++) {
          var text_node = doc.createTextNode(parts[j]);
          if(parts[j].match(/^http:\/\/[^ \t\r\n]+/)) {
            var new_node = doc.createElement('a');
            new_node.style.textDecoration='underline';
            parts[j] = parts[j].replace(/[^a-zA-Z0-9_\/]+$/, '');
            new_node.href=parts[j];
            new_node.appendChild(text_node);
            parent.insertBefore(new_node, text);
          } else {
            parent.insertBefore(text_node, text);
          }
        }
        parent.removeChild(text);
      }
    }
  }

  // is jaikungfu more-context-for-comments enabled?
  if(CJaikungfu.prefs.getBoolPref('morecontext')) {
    var comment, nick, nickurl, nickending;
    xpath = doc.evaluate(
          "//li[@class='comment']/div[@class='top']/div[@class='bottom']/p[@class='meta']/a[@title]",
          doc,
          null,
          XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
          null);
    for(i=0; i<xpath.snapshotLength; i++) {
      comment = xpath.snapshotItem(i);
      comment.firstChild.nodeValue = comment.title;

/*
      Not ready, need improved xpath. Currently becomes 'comment from ... on to #jaiku'

      match = /^http:\/\/jaiku\.com\/channel\/([a-z0-9]+)/i.exec(comment.href);
      if(match) {
        comment.parentNode.insertBefore(doc.createTextNode(' to '), comment);
        a1 = doc.createElement('a');
        a1.appendChild(doc.createTextNode('#'+match[1]));
        a1.href = 'http://jaiku.com/channel/' + match[1];
        comment.parentNode.insertBefore(a1, comment);
        comment.parentNode.insertBefore(doc.createTextNode(' '), comment);
      }
*/
      if(comment.href.match(/^http:\/\/[a-z0-9]+\.jaiku\.com/i)) {
        nick = comment.href.replace(/^http:\/\/([a-z0-9]+)\.jaiku\.com.*$/i, '$1');
        nickurl = comment.href.replace(/^(http:\/\/[a-z0-9]+\.jaiku\.com).*$/i, '$1');

        a1 = doc.createElement('a');
        a1.appendChild(doc.createTextNode(nick));
        a1.href = nickurl;
        comment.parentNode.insertBefore(a1, comment);
        if(nick.match(/s$/i)) {
          nickending = "' ";
        } else {
          nickending = "'s ";
        }
        comment.parentNode.insertBefore(doc.createTextNode(nickending), comment);
      }
    }
  }

  // Parse contact information
  if(CJaikungfu.prefs.getBoolPref('contactslist') &&
                CJaikungfu.jaikuLoggedInAs !== null &&
                /^\/contacts\/contacts/.test(pathname)) {
    CJaikungfu.parseContacts(doc);
  }

  // Add contact information
  if(CJaikungfu.prefs.getBoolPref('contactslist') &&
                CJaikungfu.jaikuLoggedInAs !== null &&
                CJaikungfu.prefs.prefHasUserValue(CJaikungfu.jaikuLoggedInAs + '_contactslist')) {
    if(CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] === undefined) {
      CJaikungfu.contactsList[CJaikungfu.jaikuLoggedInAs] = eval('(' + CJaikungfu.prefs.getCharPref(CJaikungfu.jaikuLoggedInAs + '_contactslist') + ')');
    }
    CJaikungfu.addContactInfo(doc, "//div[@id='profile']/img[@class='photo' and @alt]", 2);
    CJaikungfu.addContactInfo(doc, "//div[@id='contacts']/ul/li/div[@class='vcard']/a/img[@class='photo' and @alt]", 1);
    CJaikungfu.addContactInfo(doc, "\
//div[@id='stream']/ul[@class='stream']/li[@class]//img[@class='photo' and @alt][1]|\
//div[@id='comments']/ul/li//img[@class='photo' and @alt][1]|\
//div[@id='current-stream']//img[@class='photo' and @alt][1]", 0);
  }
/*
  var endDate = new Date();
  alert(endDate.getTime()-startDate.getTime());
*/
}

var jaikungfu = new CJaikungfu();
window.addEventListener('load', function () {
  // Add a callback to be run every time a document loads.
  // note that this includes frames/iframes within the document
  gBrowser.addEventListener('DOMContentLoaded', CJaikungfu.contentLoaded, true);
  }, false);
window.addEventListener('unload', function () {
  gBrowser.removeEventListener('DOMContentLoaded', CJaikungfu.contentLoaded, true);
  }, false);
